﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using OxyPlot;
using OxyPlot.Xps;
using System.Diagnostics;
using System.Xml;

namespace DesicionTreeViewer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private CLoadedData firstTypeData;
        private CLoadedData secondTypeData;
        private CLoadedData observableData;

        public MainWindow()
        {
            InitializeComponent();
        }

        //инициализирует все контролы начальными данными
        public void InitializeData()
        {
            if (!firstTypeData.AreSynchronized(secondTypeData)
                || !firstTypeData.AreSynchronized( observableData) )
            {
                throw new Exception("data is not synchronized!");
            }

            //заполняем все признаки. Гарантировано, что у обоих одинаковый набор признаков
            List<KeyValuePair<String, List<double>>>.Enumerator thisEnumerator = firstTypeData.Data.GetEnumerator();
            while (thisEnumerator.MoveNext())
            {
                listView1.Items.Add(thisEnumerator.Current.Key);
                listView2.Items.Add(thisEnumerator.Current.Key);
            }

            //создаем самый первый узел
            ClassificationTreeItem toBeAdd = new ClassificationTreeItem(null, firstTypeData, secondTypeData, observableData,
                0, 0, false, new ClassificationLine(), false);
            //контекстное меню
            toBeAdd.ContextMenu = treeView1.Resources["LeafContext"] as System.Windows.Controls.ContextMenu;
            
            toBeAdd.Header = "Without classification";

            treeView1.Items.Add(toBeAdd as TreeViewItem);
            toBeAdd.IsSelected = true;
        }

        //создает облако точек из dataStorage.
        //pointsClasterName - название облака точек
        //filter - фильтр
        //dataStorage - точки
        //firstProperty,secondProperty - признаки
        //hasToDrawLine - нужно ли учитывать разделяющую прямую при фильтрации
        //classificationLine - разделяющая прямая
        //isUnderLineDrawingMode - нужно отфильтровать точки под прямой.
        private ScatterSeries extractPointsFromData(String pointsClasterName, HashSet<int> filter, CLoadedData dataStorage,
            int firstProperty, int secondProperty, bool hasToDrawLine, ClassificationLine classificationLine, bool isUnderLineDrawingMode )
        {
            if (dataStorage.TrainingDataSize == 0)
            {
                return null;
            }

            ScatterSeries data = new ScatterSeries(pointsClasterName );

            //получим нужные точки
            List<double> allXPoints = dataStorage.Data[firstProperty].Value;
            List<double> allYPoints = dataStorage.Data[secondProperty].Value;
 
            int curIndex = 0;
            List<double>.Enumerator xEnumerator = allXPoints.GetEnumerator();
            List<double>.Enumerator yEnumerator = allYPoints.GetEnumerator();

            while ( xEnumerator.MoveNext() && yEnumerator.MoveNext() )
            {
                //проверка фильтром
                if (!filter.Contains(curIndex))
                {
                    DataPoint candidatePoint =
                        new DataPoint(xEnumerator.Current, yEnumerator.Current);

                    if (hasToDrawLine)
                    {
                        //учитываем линию
                        if ((isUnderLineDrawingMode && classificationLine.IsUnderLine(candidatePoint))
                            || (!isUnderLineDrawingMode && !classificationLine.IsUnderLine(candidatePoint)))
                        {
                            data.Points.Add(candidatePoint);
                        }
                    }
                    else
                    {
                        //не учитываем линию
                        data.Points.Add(candidatePoint);
                    }
                    
                }
                curIndex++;
            }

            //добавляем кол-во точек к названию
            data.Title += "( " + data.Points.Count.ToString() + " )";

            return data;
        }

        private double findMin(IEnumerable<double> collection)
        {
            IEnumerator<double> enumerator = collection.GetEnumerator();
            double curMin = double.MaxValue;
            while (enumerator.MoveNext()) 
            {
                if ( enumerator.Current < curMin)
                {
                    curMin = enumerator.Current;
                }
            }

            return curMin;
        }

        private double findMax(IEnumerable<double> collection)
        {
            IEnumerator<double> enumerator = collection.GetEnumerator();
            double curMax = double.MinValue;
            while (enumerator.MoveNext())
            {
                if ( enumerator.Current > curMax)
                {
                    curMax = enumerator.Current;
                }
            }

            return curMax;
        }

        //рисует точки на графике. 
        //firstTypeDrawibleIndices, secondTypeDrawibleIndices,observationDrawibleIndeces -
        //- индексы в CLoadedData.Data элементов, которые буду нарисованы
        //firstProperty, secondProperty - признаки, по которым будет рисование
        //classificationLine - классифицирующая прямая
        //isUnderLineDrawingMode - нужно ли нарисовать точки, которые лежат под прямой или над прямой
        private void drawPointsOnPlot(HashSet<int> firstTypeDrawibleIndices,
            HashSet<int> secondTypeDrawibleIndices, HashSet<int> observationDrawibleIndeces,
            int firstProperty, int secondProperty, bool hasToDrawLine, ClassificationLine classificationLine,
            bool isUnderLineDrawingMode)
        {

            PlotModel plotModel = new PlotModel("Manual classification");

            ScatterSeries secondTypeDataPoints = extractPointsFromData("SecondType", firstTypeDrawibleIndices,
                firstTypeData, firstProperty, secondProperty, hasToDrawLine,
                classificationLine, isUnderLineDrawingMode);
            secondTypeDataPoints.MarkerStroke = OxyColors.Blue;

            ScatterSeries firstTypeDataPoints = extractPointsFromData("FirstType", secondTypeDrawibleIndices,
                secondTypeData, firstProperty, secondProperty, hasToDrawLine,
                classificationLine, isUnderLineDrawingMode);
            firstTypeDataPoints.MarkerStroke = OxyColors.Red;

            ScatterSeries observablePointsData = extractPointsFromData("Observable data", observationDrawibleIndeces,
                observableData, firstProperty, secondProperty, hasToDrawLine, classificationLine, isUnderLineDrawingMode);
            if (observablePointsData != null)
            {
                observablePointsData.MarkerStroke = OxyColors.LemonChiffon;
            }

            if (secondTypeDataPoints.Points.Count == 0 && firstTypeDataPoints.Points.Count == 0)
            {
                GraphPlotter.Model = plotModel;
                return;
            }

            IEnumerable<double> secondTypeObjectsY =
                from DataPoint pt in secondTypeDataPoints.Points select pt.Y;
            IEnumerable<double> secondTypeObjectsX =
                from DataPoint pt in secondTypeDataPoints.Points select pt.X;

            IEnumerable<double> firstTypeObjectsY =
                from DataPoint pt in firstTypeDataPoints.Points select pt.Y;
            IEnumerable<double> firstTypeObjectsX =
                from DataPoint pt in firstTypeDataPoints.Points select pt.X;

            double minY = Math.Min(findMin(secondTypeObjectsY), findMin(firstTypeObjectsY));
            double maxY = Math.Max(findMax(secondTypeObjectsY), findMax(firstTypeObjectsY));
            double minX = Math.Min(findMin(secondTypeObjectsX), findMin(firstTypeObjectsX));
            double maxX = Math.Max(findMax(secondTypeObjectsX), findMax(firstTypeObjectsX));

            plotModel.Series.Add(secondTypeDataPoints);
            plotModel.Series.Add(firstTypeDataPoints);

            if (observablePointsData != null)
            {
                plotModel.Series.Add(observablePointsData);
            }

            plotModel.Axes.Add(new LinearAxis(AxisPosition.Left, double.IsInfinity(minY) ? 0 : minY, double.IsInfinity(maxY) ? 10 : maxY ));
            plotModel.Axes.Add(new LinearAxis(AxisPosition.Bottom, double.IsInfinity(minX) ? 0 : minX, double.IsInfinity(maxX) ? 10 : maxX));

            plotModel.Annotations.Clear();
            plotModel.Annotations.Add(classificationLine);
            GraphPlotter.Model = plotModel; 
        }

        private int currentNodeNumber;
        private void writeNodesToFile(XmlTextWriter writer, ClassificationTreeItem currentItem)
        {

            if (currentItem == null)
            {
                return;
            }

            writer.WriteStartElement("DecisionNode");

            //записываем номер
            writer.WriteStartAttribute("NodeNumber");
            writer.WriteString(currentNodeNumber.ToString());
            writer.WriteEndAttribute();

            currentNodeNumber++;

            if (!currentItem.Items.IsEmpty)
            {
                //есть дочерние узлы
                ClassificationTreeItem childItem = currentItem.Items[0] as ClassificationTreeItem;

                //сохраняем прямую
                writer.WriteStartAttribute("Slope");
                writer.WriteString(childItem.Line.Slope.ToString());
                writer.WriteEndAttribute();
                writer.WriteStartAttribute("Intercept");
                writer.WriteString(childItem.Line.Intercept.ToString());
                writer.WriteEndAttribute();

                //сохраняем названия признаков
                writer.WriteStartAttribute("XProperty");
                writer.WriteString(listView1.Items[childItem.FirstProperty] as String);
                writer.WriteEndAttribute();

                writer.WriteStartAttribute("YProperty");
                writer.WriteString(listView1.Items[childItem.SecondProperty] as String);
                writer.WriteEndAttribute();
            }
            else
            {
                if (currentItem.LeafType == TLeafType.LT_FirstTypeNode)
                {
                    writer.WriteStartAttribute("IsFirstTypeOBject");
                    writer.WriteString(true.ToString());
                    writer.WriteEndAttribute();
                }
                else
                {
                    writer.WriteStartAttribute("IsFirstTypeOBject");
                    writer.WriteString(false.ToString());
                    writer.WriteEndAttribute();
                }
            }

            writer.WriteStartAttribute( "GetUnderLinePoints" );
            writer.WriteString( ( !currentItem.FilterUnderLinePoint ).ToString() );
            writer.WriteEndAttribute();

            for (int i = 0; i < currentItem.Items.Count; i++)
            {
                writeNodesToFile(writer, (currentItem.Items[i] as ClassificationTreeItem ) );
            }

            writer.WriteEndElement();
        }

        private void writeTreeToXMLFile(String fileName)
        {
            XmlTextWriter textWriter = new XmlTextWriter(fileName, null);
            // Opens the document
            textWriter.WriteStartDocument();
            textWriter.Formatting = Formatting.Indented;
            // Write comments
            textWriter.WriteComment("Desicion tree");

            currentNodeNumber = 1;

            writeNodesToFile( textWriter, treeView1.Items.IsEmpty ? null : ( treeView1.Items[0] as ClassificationTreeItem) );

            // Ends the document.
            textWriter.WriteEndDocument();
            // close writer
            textWriter.Close();
        }

        private void OpenClickHandler(object sender, RoutedEventArgs e)
        {
            //создаем диалоговое окно
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

            //устанавливаем параметры диалога
            dlg.DefaultExt = ".xlsx";
            dlg.Filter = "Excel document (.xlsx)|*.xlsx";

            //показываем окно
            Nullable<bool> result = dlg.ShowDialog();

            //вытаскиваем, что получилось
            if (result == true)
            {
                DataLoader dataLoader = new DataLoader();
                if (dataLoader.LoadExcelFile(dlg.FileName))
                {
                    firstTypeData = dataLoader.FirstTypeData;
                    secondTypeData = dataLoader.SecondTypeData;
                    observableData = dataLoader.ObservableData;
                    InitializeData();
                }
            }
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            ClassificationTreeItem selectedParent = treeView1.SelectedItem as ClassificationTreeItem;

            if (selectedParent == null)
            {
                MessageBox.Show("choose base node, please");
                return;
            }

            drawPointsOnPlot(selectedParent.SecondTypeDataFilter, selectedParent.FirstTypeDataFilter, selectedParent.ObservationFilter, listView1.SelectedIndex,
                listView2.SelectedIndex, false, GraphPlotter.Model.Annotations[0] as ClassificationLine, true);
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {

            ClassificationTreeItem selectedParent = treeView1.SelectedItem as ClassificationTreeItem;
            Debug.Assert( selectedParent != null );

            ClassificationTreeItem toBeAddYes = new ClassificationTreeItem(selectedParent, firstTypeData, secondTypeData, observableData,
                listView1.SelectedIndex, listView2.SelectedIndex, true, GraphPlotter.Model.Annotations[0] as ClassificationLine, true );

            ClassificationTreeItem toBeAddNo = new ClassificationTreeItem(selectedParent, firstTypeData, secondTypeData, observableData, 
                listView1.SelectedIndex, listView2.SelectedIndex, false, GraphPlotter.Model.Annotations[0] as ClassificationLine, true);

            toBeAddYes.Header = "right";
            toBeAddNo.Header = "left";

            toBeAddYes.ContextMenu = treeView1.Resources["LeafContext"] as System.Windows.Controls.ContextMenu;
            toBeAddNo.ContextMenu = treeView1.Resources["LeafContext"] as System.Windows.Controls.ContextMenu;
            toBeAddYes.KeyDown += OnItemKeyDown;
            toBeAddNo.KeyDown += OnItemKeyDown;

            selectedParent.Items.Add(toBeAddYes as TreeViewItem);
            selectedParent.Items.Add(toBeAddNo as TreeViewItem);

            toBeAddNo.IsSelected = true;
        }

        private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            ClassificationTreeItem selectedItem= treeView1.SelectedItem as ClassificationTreeItem;
            if (selectedItem == null)
            {
                return;
            }

            listView1.SelectedIndex = selectedItem.FirstProperty;
            listView2.SelectedIndex = selectedItem.SecondProperty;

            //устанавливаем контекстное меню
            (selectedItem.ContextMenu.Items[0] as MenuItem).IsChecked = selectedItem.LeafType == TLeafType.LT_FirstTypeNode;
            (selectedItem.ContextMenu.Items[1] as MenuItem).IsChecked = selectedItem.LeafType == TLeafType.LT_SecondTypeNode;

            drawPointsOnPlot(selectedItem.SecondTypeDataFilter, selectedItem.FirstTypeDataFilter, selectedItem.ObservationFilter, selectedItem.FirstProperty,
                selectedItem.SecondProperty, selectedItem.HasClassificationLine, selectedItem.Line, !selectedItem.FilterUnderLinePoint);
            GraphPlotter.RefreshPlot(true);
        }

        private void OnSaveTree(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.SaveFileDialog saveFileDialog1 = new Microsoft.Win32.SaveFileDialog();

            saveFileDialog1.Filter = "xml file (*.xml)|*.xml";
            saveFileDialog1.RestoreDirectory = true;

            //показываем окно
            Nullable<bool> result = saveFileDialog1.ShowDialog();

            //вытаскиваем, что получилось
            if (result == true)
            {
                writeTreeToXMLFile(saveFileDialog1.FileName);
            }
        }

        //находит индекс признака в listview признаков
        private int getPropertyIndex(String propertyString)
        {
            for (int i = 0; i < listView1.Items.Count; i++)
            {
                if (listView1.Items.GetItemAt(i).ToString() == propertyString)
                {
                    return i;
                }
            }

            Debug.Assert(false);
            return -1;
        }

        private void adjustNextNode(XmlNode parentNode, ClassificationTreeItem parentTreeNode )
        {
            //прочитаем свойства нода
            XmlAttributeCollection attributes = parentNode.Attributes;

            double slope = 0;
            double intercept = 0;
            bool hasToGetUnderlinePoints = false;
            int firstPropertyIndex = -1;
            int secondPropertyIndex = -1;
            bool isFirstTypeObject = false;

            bool isLeafNode = false;
            //пробегаем по атрибутам
            for (int i = 0; i < attributes.Count; i++)
            {
                String name = attributes.Item(i).Name;
                String value = attributes.Item(i).Value;

                if (name == "Slope")
                {
                    slope = Convert.ToDouble( value );
                }
                else if (name == "Intercept")
                {
                    intercept = Convert.ToDouble( value );
                }
                else if (name == "XProperty")
                {
                    firstPropertyIndex = getPropertyIndex(value);
                }
                else if (name == "YProperty")
                {
                    secondPropertyIndex = getPropertyIndex(value);
                }
                else if (name == "IsFirstTypeOBject")
                {
                    isFirstTypeObject = Convert.ToBoolean( value );
                    isLeafNode = true;
                }
                else if (name == "GetUnderLinePoints")
                {
                    hasToGetUnderlinePoints = Convert.ToBoolean(value);
                }
            }

            if (isLeafNode)
            {
                if (isFirstTypeObject)
                {
                    parentTreeNode.LeafType = TLeafType.LT_FirstTypeNode;
                }
                else
                {
                    parentTreeNode.LeafType = TLeafType.LT_NotDefined;
                }

                return;
            }

            //создаем дочерние узлы
            ClassificationLine childNodeLine = new ClassificationLine();
            childNodeLine.Slope = slope;
            childNodeLine.Intercept = intercept;

            ClassificationTreeItem rightChildNode = new ClassificationTreeItem(parentTreeNode, firstTypeData, secondTypeData, observableData,
                firstPropertyIndex, secondPropertyIndex, true, childNodeLine.Clone(), true);
            rightChildNode.Header = "right";
            rightChildNode.ContextMenu = treeView1.Resources["LeafContext"] as System.Windows.Controls.ContextMenu;
            rightChildNode.KeyDown += OnItemKeyDown;

            ClassificationTreeItem leftChildNode = new ClassificationTreeItem(parentTreeNode, firstTypeData, secondTypeData, observableData,
                firstPropertyIndex, secondPropertyIndex, false, childNodeLine.Clone(), true);
            leftChildNode.Header = "left";
            leftChildNode.ContextMenu = treeView1.Resources["LeafContext"] as System.Windows.Controls.ContextMenu;
            leftChildNode.KeyDown += OnItemKeyDown;

            //связываем дочерние узлы с родительским
            parentTreeNode.Items.Add(rightChildNode);
            parentTreeNode.Items.Add(leftChildNode);

            adjustNextNode(parentNode.FirstChild, rightChildNode);
            adjustNextNode(parentNode.LastChild, leftChildNode);
        }

        private void adjustNewTree( String fileName )
        {
            //удаляем старое дерево
            treeView1.Items.Clear();

            //открыли файл
            XmlTextReader reader = new XmlTextReader(fileName);

            //создали докумен
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(reader);

            //создаем самый первый узел
            ClassificationTreeItem rootItem = new ClassificationTreeItem(null, firstTypeData, secondTypeData, observableData,
                0, 0, false, new ClassificationLine(), false);
            rootItem.Header = "root";
            rootItem.ContextMenu = treeView1.Resources["LeafContext"] as System.Windows.Controls.ContextMenu;

            treeView1.Items.Add(rootItem);

            adjustNextNode(xmlDocument.LastChild, rootItem);

        }

        private void OnOpenTree(object sender, RoutedEventArgs e)
        {
            //создаем диалоговое окно
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

            //устанавливаем параметры диалога
            dlg.DefaultExt = ".xml";
            dlg.Filter = "XML document (.xml)|*.xml";

            //показываем окно
            Nullable<bool> result = dlg.ShowDialog();

            //вытаскиваем, что получилось
            if (result == true)
            {
                adjustNewTree(dlg.FileName);
            }
        }

        private void OnCloseData(object sender, RoutedEventArgs e)
        {
            if (!treeView1.Items.IsEmpty)
            {
                MessageBoxResult result = MessageBox.Show("Сохранить дерево?", "Сохранение", MessageBoxButton.YesNoCancel);

                if (result == MessageBoxResult.Yes)
                {
                    OnSaveTree(sender, e);
                }
                else if (result == MessageBoxResult.Cancel)
                {
                    //уходим - пользователь передумал.
                    return;
                }
            }

            treeView1.Items.Clear();
            listView1.Items.Clear();
            listView2.Items.Clear();
            GraphPlotter.Model = null;
        }

        private void OnIsFirstTypeObject(object sender, RoutedEventArgs e)
        {
            ClassificationTreeItem selectedItem = treeView1.SelectedItem as ClassificationTreeItem;
            if ((selectedItem.ContextMenu.Items[0] as MenuItem).IsChecked)
            {
                selectedItem.LeafType = TLeafType.LT_FirstTypeNode;
                (selectedItem.ContextMenu.Items[1] as MenuItem).IsChecked = false;
            }
            else
            {
                selectedItem.LeafType = TLeafType.LT_NotDefined;
            }
        }

        private void OnIsSecondTypeObject(object sender, RoutedEventArgs e)
        {
            ClassificationTreeItem selectedItem = treeView1.SelectedItem as ClassificationTreeItem;
            if ((selectedItem.ContextMenu.Items[1] as MenuItem).IsChecked)
            {
                selectedItem.LeafType = TLeafType.LT_SecondTypeNode;
                (selectedItem.ContextMenu.Items[0] as MenuItem).IsChecked = false;
            }
            else
            {
                selectedItem.LeafType = TLeafType.LT_NotDefined;
            }
        }

        private void OnItemKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.Delete)
            {
                (treeView1.SelectedItem as TreeViewItem).Items.Clear();

            }
        }

        private void ContextMenu_Opened(object sender, RoutedEventArgs e)
        {
        }
        
    }
}
